前面有提到學習的第三、第四階段希望來寫一個簡單函式與 Rust CLI 工具,因為時間也所剩不多,今天就結合在一起來學吧!
從官方教學文件中有個經典的入門猜數字遊戲範例,再來看看其中有什麼可以再補上的基本語法與觀念,或許能碰上第一個坎中的所有權。
可以沿用昨天的專案來改寫其中的 main.rs
,或直接用 cargo new
再建立一個新專案都可以:
$ cargo new guessing_game
$ cd guessing_game
$ code .
💡 BTW,
code .
是 VS Code 快速開啟當前資料夾的指令,或用自己習慣的編輯器開啟專案也可以
安裝 rand 這個 crate,因為 Rust 本身沒也產亂數的函式庫,這裡會需要安裝套件:
$ cargo add rand
參考官方的範例先試著放到 main.rs
中,並補上一些簡單的邏輯說明:
// main.rs
// 導入需要的函式庫到這個作用域中
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("請猜測一個 1-100 間的數字:");
// 產生一個隨機數字
let secret_number = rand::thread_rng().gen_range(1..=100);
// while loop 直到中斷
loop {
println!("請輸入你的猜測數字。");
// 宣告一個變數來接使用者從 CLI 上的輸入
let mut guess = String::new();
// 利用 io 標準函式庫來讀取值
// 並寫入到 guess 中
io::stdin().read_line(&mut guess).expect("讀取該行失敗");
// 字串轉數字
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("你的猜測數字:{guess}");
// 比對數字並印出對應結果
match guess.cmp(&secret_number) {
Ordering::Less => println!("太小了!"),
Ordering::Greater => println!("太大了!"),
Ordering::Equal => {
println!("獲勝!");
break;
}
}
}
}
嘗試用 cargo run
編譯並執行試試效果:
可以看到這段程式執行後可以在 CLI 上與使用者互動,玩類似終極密碼的遊戲,那麼以下我們就來補一下不熟悉的觀念吧。
rand::Rng
?首先看到最開頭函式庫引入的地方,我有點好奇 rand::Rng
是什麼,這裡去 crates.io 上查 rand 這個 crate 的文件,會寫道:
Easy random value generation and usage via the
Rng
,SliceRandom
andIteratorRandom
traits
這個 traits
又是什麼呢?如果點進 Rng 的介紹會看到這樣的程式:
pub trait Rng: RngCore {
fn gen<T>(&mut self) -> T
where
Standard: Distribution<T>,
{ ... }
fn gen_range<T, R>(&mut self, range: R) -> T
where
T: SampleUniform,
R: SampleRange<T>,
{ ... }
...
}
看起來這個 traits
是定義了許多空函式,再到 traits 的文件與龍哥這篇比較淺顯易懂的介紹會得到幾個重點:
traits
類似 interface
或 abstract class
的概念。白話地講的話就是定義好某個特徵函式,其他類別可以去自己實作自己要的樣子Class
概念被稱為 struct
,可以用 impl…for
的語法來替每個類別去實現 (implement) 他的特徵參考文章中的範例看個感覺:
// 定義一個「可飛行」的特徵
trait Flyable {
fn fly(&self);
}
// 定義一個 Cat 類別
struct Cat {
name: String,
}
// 為 Cat 實現「可飛行」的特徵
impl Flyable for Cat {
fn fly(&self) {
println!("看啊,那隻貓 {} 竟然會飛!", self.name);
}
}
struct Bird {
species: String,
}
impl Flyable for Bird {
fn fly(&self) {
println!("這隻 {} 正在飛行!", self.species);
}
}
回到原本想了解的 rand::Rng
與下面的這段 let secret_number = rand::thread_rng().gen_range(1..=100);
,可以知道其中的 thread_rng 是 rand 中的一個 struct
,而其中有去實現了 gen_range
這個 trait
。
💡 順帶補充一下,其中的這個
[1..=100]
指的是範圍在1~100
間,需要寫等號的原因是預設如果只寫[1..100]
的話會不包含右邊界 (ref)。
&mut
?在這段程式中,我們會看到一個 &mut
:
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取該行失敗");
這是什麼意思呢?
先講個簡單的,這個 mut
在 Rust 中指的是 mutable 的意思,在 Rust 中宣告變數時,雖然常會用跟 JavaScript 有點像的 let
來宣告變數,但如果是要可變的話,就會需要這要寫:
fn main() {
let foo = 5566;
let mut bar = 1234;
// 這行會導致編譯錯誤,因為 foo 是不可變的
foo = 7788;
// 這是允許的,因為 bar 有加上 mut
bar = 7788;
}
而至於為什麼要放上 &
呢,這個就會牽扯到 Rust 的一大特性 —— 所有權 (Ownership),因為是大魔王,所以待我明天繼續來往下研究。
今天效法 RBE (Rust By Example) 的精神,嘗試直接用一些簡單的範例來回頭學習語法,或許這對有其他語言底子或不想看整本 The Book 的人來說會是最快速的方式。
今天從一個 rand 的引入中就一次補齊了 struct
與 traits
的基本概念,雖然其中還有許多進階內容,但先理解到是什麼、怎麼用後,未來有需要再回來補上。